import * as step from '@flow-step/step-toolkit'
import * as process from 'process'
import * as util from './util'
import * as path from 'path'
import * as fs from 'fs'
import * as retryHelper from './retry-helper'
import * as commitUtil from './git-commit-message'
import * as svnSource from './svn-source'
import * as ossUtil from './oss-api-helper'
import * as inputCheck from './input-check'
import * as authUtil from './auth-helper'
import * as packagesSource from './packages-source'
import {IGitSourceParams} from './git-source-params'
import {GitTools} from './git-command-helper'
import {updateGitConfigSettings} from './git-config-settings'
import axios from 'axios'
import {masking} from './util'

// 安装 对应版本Git
// export async function installGit(): Promise<void> {
//     let arch: string = step.platform.getArch()
//     let platform: string = step.platform.getPlatform()
//     let toolName: string = 'git'
//     let gitVersion = 'v2.12.0'
//
//     // 优先从缓存中查找
//     let cachedGitVersions: string[] = await step.tool.whichAllVersions(toolName)
//     for (let cachedGitVersion of cachedGitVersions) {
//         if (cachedGitVersion === gitVersion) {
//             let foundGitPath = await step.tool.which(toolName, gitVersion)
//             step.info(
//                 `Found cached maven version ${cachedGitVersion} in ${foundGitPath}, skip the installation.`
//             )
//             step.addPath(path.join(foundGitPath, 'bin'))
//             return
//         }
//     }
//
//     let toolRegistryBaseUrl = getToolRegistryBaseUrl()
//     if (toolRegistryBaseUrl === undefined) {
//         step.error(
//             "Install git failed, can not resolve tool registry base url from 'FLOW_TOOL_REGISTRY_URL'"
//         )
//         throw new Error('resolve tool registry failed')
//     }
//
//     let downloadUrl = getToolDownloadUrl(
//         toolRegistryBaseUrl,
//         toolName,
//         gitVersion,
//         platform,
//         arch
//     )
//
//     let gitDownloadPath = await step.tool.download(downloadUrl)
//     let gitExtractedDir = await step.tool.extract(gitDownloadPath)
//     let gitCachedPath = await step.tool.cacheDir(
//         gitExtractedDir,
//         toolName,
//         gitVersion
//     )
//
//     step.addPath(path.join(gitCachedPath, 'bin'))
//     step.info(`Install GIT ${gitVersion} successfully`)
//     step.info(`  git version : ${gitVersion}`)
//     step.info(`  install path: ${gitExtractedDir}`)
//     step.info(`  source      : ${downloadUrl}`)
// }
//
// export function getToolDownloadUrl(
//     toolRegistryBaseUrl: string,
//     toolName: string,
//     toolVersion: string,
//     platform: string,
//     arch: string
// ) {
//     return `${toolRegistryBaseUrl}/${toolName}/${toolName}-${toolVersion}-${platform}-${arch}.tar.gz`
// }
//
// export function getToolRegistryBaseUrl(): string | undefined {
//     return process.env['FLOW_TOOL_REGISTRY_URL']
// }

export async function execute(): Promise<void> {
    step.info(`Going to execute git clone step`)
    const sourceParamsArray = await inputCheck.getInputs()
    for (let i = 0; i < sourceParamsArray.length; i++) {
        const sourceParams = sourceParamsArray[i]
        if (sourceParams.type === 'flow') {
            await handleFlowSource(sourceParams)
        } else if (sourceParams.type === 'jenkins') {
            await handleJenkinsSource(sourceParams)
        } else if (sourceParams.type === 'svn') {
            const result = await new svnSource.SvnSource().handleSvnSource(sourceParams)
            if(result != 0) {
                process.exit(result)
            }
        } else if (sourceParams.type === 'packages') {
            await packagesSource.createPackagesSource().handlePackagesSource(sourceParams)
        } else {
            //get user and password for different code source
            let user = authUtil.getUser(sourceParams.type, sourceParams.user)
            let password = authUtil.getPassword(
                sourceParams.type,
                sourceParams.password
            )
            //get cloneUrl for different host policy(http/ssh)
            let cloneUrl = authUtil.getCloneUrl(
                user,
                password,
                sourceParams.gitRepo,
                sourceParams.hostGitMethod,
                sourceParams.httpPolicy,
                sourceParams.gitEndpoint
            )
            //update the field
            sourceParams.cloneUrl = cloneUrl
            sourceParams.user = user
            sourceParams.password = password
            if (sourceParams.hostGitMethod === 'http') {
                sourceParams.httpUrlWithCredential = true
            } else if (sourceParams.hostGitMethod === 'ssh') {
                sourceParams.sshUrl = true
            }
            //ignore git ssl certificate
            //await util.ignoreGitSsl(sourceParams.workDir);
            if (fs.existsSync(`${sourceParams.projectDir}${util.SEP}.git`)) {
                // 如果有异常，就重新执行clone
                try {
                    await doFetch(sourceParams)
                    step.info("fetch success")
                }catch (err){
                    step.error("fetch repo error "+masking(err))
                    step.info("begin to clone repo")
                    await doClone(sourceParams)
                }
            } else {
                await doClone(sourceParams)
            }
            //write commit info
            let sourceIndex = i + 1
            if (sourceParams.order != undefined && sourceParams.order != null) {
                sourceIndex = sourceParams.order
            }
            await commitUtil.recordInfo(sourceParams, sourceIndex)
            await commitUtil.calculateCommitRange(sourceParams)
            step.info(``)
            step.info(`----------------------------------------------`)
            step.info(``)
        }
    }
}

export async function doFetch(input: IGitSourceParams) {
    let git = new GitTools()
    if (fs.existsSync(`${input.projectDir}${util.SEP}.git/index.lock`)) {
        fs.unlinkSync(`${input.projectDir}${util.SEP}.git/index.lock`)
    }
    let configOutput = await git.getRemoteUrl(input.projectDir)
    if (configOutput.exitCode != 0) {
        step.info(`git config --get remote.origin.url error`)
        throw new Error('Get remote origin url error')
    }
    let remoteUrl = configOutput.stdout
    let remoteEndpoint = remoteUrl.replace(RegExp(`https?:\/\/.*@`), '')
    let branches = (await git.getlocalBranches(input.projectDir)).stdout
    let currentBranch = branches.substring(branches.indexOf('*')).split(' ')[1]
    if (currentBranch.endsWith(",")) {
        currentBranch = currentBranch.slice(0, -1)
    }
    step.info(`input branch is ${input.branch} and currentBranch is ${currentBranch}`)
    if (
        input.branch != undefined &&
        currentBranch === input.branch &&
        (remoteUrl === input.gitRepo || remoteEndpoint === input.gitEndpoint)
    ) {
        //clean local changes not commit
        await cleanLocalChange(input.projectDir, input.submodule)
        //sync code
        if (input.httpUrlWithCredential === true) {
            let output = await git.updateRemoteUrl(
                input.projectDir,
                input.cloneUrl
            )
            if (output.exitCode != 0) {
                step.info(`git config remote.origin.url error`)
                throw new Error('Update remote origin url error')
            }
        }
        if (
            input.branch.match(RegExp(`refs/merge-requests/[1-9][0-9]*/head`)) != null ||
            input.branch.match(RegExp(`refs/changes/[1-9][0-9]*/head`)) != null
        ) {
            await handleMergeRequestOrChange(input.projectDir, input.workDir, input.branch, branches, git);
        } else {
            step.info(`sync origin code: git fetch origin ${input.branch}`)
            await git.sshCommand(
                input.projectDir,
                `ssh -i '${input.workDir}${util.SEP}tempssh${util.SEP}id_rsa_custom'`
            )
            await git.fetch(input.projectDir, 'origin', `${input.branch}`)
            await git.fetch(input.projectDir, 'origin')
            await git.reset(input.projectDir, `origin/${input.branch}`, '--hard')
        }
        if (input.commitId != undefined && input.commitId != 'null') {
            step.info('There are some new commits, please rerun!')
            await git.reset(input.projectDir, input.commitId, '--hard')
        }
        if (input.submodule === true) {
            step.info('git fetch with submodule')
            await git.submodule(input.projectDir, 'update', '--init', '--recursive')
        }
    } else if (
        input.branch != undefined &&
        currentBranch != input.branch &&
        (remoteUrl === input.gitRepo || remoteEndpoint === input.gitEndpoint)
    ) {
        //clean local changes not commit
        await cleanLocalChange(input.projectDir, input.submodule)
        //sync code
        if (input.httpUrlWithCredential === true) {
            let output = await git.updateRemoteUrl(
                input.projectDir,
                input.cloneUrl
            )
            if (output.exitCode != 0) {
                step.info(`git config remote.origin.url error`)
                throw new Error('Update remote origin url error')
            }
        }
        if (
            input.branch.match(RegExp(`refs/merge-requests/[1-9][0-9]*/head`)) != null ||
            input.branch.match(RegExp(`refs/changes/[1-9][0-9]*/head`)) != null
        ) {
            await handleMergeRequestOrChange(input.projectDir, input.workDir, input.branch, branches, git);
        } else {
            //checkout new branch
            step.info(`input branch not equal current branch: git fetch origin ${input.branch}`)
            await git.sshCommand(
                input.projectDir,
                `ssh -i '${input.workDir}${util.SEP}tempssh${util.SEP}id_rsa_custom'`
            )
            await git.fetch(input.projectDir, 'origin', `${input.branch}`)
            await git.fetch(input.projectDir, 'origin')
            await git.fetch(input.projectDir, '--tags')
            await git.checkout(input.projectDir, input.branch)
            await git.reset(input.projectDir, `origin/${input.branch}`, '--hard')
        }
        if (input.submodule === true) {
            step.info('git fetch with submodule')
            await git.submodule(input.projectDir, 'update', '--init', '--recursive')
        }
    } else {
        step.info(`git fetch failed: git fetch origin ${input.branch} from ${remoteUrl}`)
        throw new Error('git fetch error')
    }
}

async function handleMergeRequestOrChange(
    projectDir: string,
    workDir: string,
    inputBranch: string,
    branches: string,
    git: GitTools) {
    let remoteRef = inputBranch.replace('refs/', '')
    let replaceValue = inputBranch.match(RegExp(`[1-9][0-9]*`))
    let tmpLocalBranch = ''
    if (inputBranch.match(RegExp(`refs/merge-requests/[1-9][0-9]*/head`)) != null) {
        tmpLocalBranch = inputBranch.replace(
            RegExp(`refs/merge-requests/[1-9][0-9]*/head`),
            'merge-request_' + replaceValue
        )
    } else {
        tmpLocalBranch = inputBranch.replace(
            RegExp(`refs/changes/[1-9][0-9]*/head`),
            'code-review_' + replaceValue
        )
    }
    if (branches.match(tmpLocalBranch) != null) {
        let output = await git.deleteBranch(
            projectDir,
            tmpLocalBranch
        )
        if (output.exitCode != 0) {
            step.info(`git branch -D ${tmpLocalBranch} error`)
            throw new Error('Delete local temp branch error')
        }
    }
    step.info(`[Checkout_temp_branch] git fetch origin ${remoteRef}:${tmpLocalBranch}`)
    await git.sshCommand(
        projectDir,
        `ssh -i '${workDir}${util.SEP}tempssh${util.SEP}id_rsa_custom'`
    )
    let fetchOutput = await git.fetch(
        projectDir,
        'origin',
        `${remoteRef}:${tmpLocalBranch}`
    )
    if (fetchOutput.exitCode != 0) {
        step.info(
            `git fetch origin ${remoteRef}:${tmpLocalBranch} error`
        )
        throw new Error('git fetch error')
    }
    await git.checkout(projectDir, tmpLocalBranch)
}

export async function doClone(input: IGitSourceParams) {
    let git = new GitTools()
    
    // 检查是否应该使用内网域名
    const usePublicCluster = process.env['NODEGROUP_TYPE'] === 'K8S';
    const flowFeatureCodeupInternalDomain = process.env.FLOW_FEATURE_CODEUP_INTERNAL_DOMAIN;
    const flowFeatureCodeupInternalIP = process.env.FLOW_FEATURE_CODEUP_INTERNAL_IP;
    const hasInnerHostConfig = flowFeatureCodeupInternalDomain && flowFeatureCodeupInternalIP;
    
    if (usePublicCluster && hasInnerHostConfig) {
        step.info(`FLOW_FEATURE_CODEUP_INTERNAL_DOMAIN = ${flowFeatureCodeupInternalDomain}`);
        step.info(`FLOW_FEATURE_CODEUP_INTERNAL_IP = ${flowFeatureCodeupInternalIP}`);
        step.info(`Using inner host for clone: ${flowFeatureCodeupInternalDomain}`);
    }

    // 首先尝试使用内网域名（如果配置了的话）
    if (usePublicCluster) {
        try {
            await updateGitConfigSettings('add', input, true, git.updateConfig);
            await retryHelper.execute(async () => {
                return await performClone(input, git);
            });
            step.info("Clone with inner host successful");
            // 克隆成功后，清除内网配置并设置外网配置
            await updateGitConfigSettings('unset', input, true, git.updateConfig);
            await updateGitConfigSettings('add', input, false, git.updateConfig);
            return;
        } catch (err) {
            step.warning(`Clone with inner host failed: ${masking(err)}`);
            step.info("Falling back to outer host for clone");
            // 清除内网配置
            await updateGitConfigSettings('unset', input, true, git.updateConfig);
            // insteadOf还是要的，不然如果submodule是ssh地址的，还是会报错，但是insteadOf中就不要使用内网地址
            await updateGitConfigSettings('add', input, false, git.updateConfig);
            await retryHelper.execute(async () => {
                return await performClone(input, git);
            });
            return
        }
    }

    await retryHelper.execute(async () => {
        return await performClone(input, git);
    });
}

async function performClone(input: IGitSourceParams, git: GitTools): Promise<void> {
        // input.projectDir should be clear before git clone
        if (fs.existsSync(input.projectDir)) {
            util.clearDir(input.projectDir)
        } else {
            fs.mkdirSync(input.projectDir)
        }

        let exitCode = -1
        let arg1 = ''
        let arg2 = ''
        let arg3 = ''
        if (input.submodule == true) {
            step.info('git clone with submodule')
            arg1 = '--recurse-submodules'
        }
        if (input.depth != 0) {
            step.info('git clone with clone depth')
            arg2 = `--depth=${input.depth}`
        }
        if (input.sshUrl === true) {
            arg3 = `core.sshCommand=ssh -i '${input.workDir}${util.SEP}tempssh${util.SEP}id_rsa_custom' -o StrictHostKeyChecking=no`
            step.info(arg3)
        }
        if (
            input.branch != undefined &&
            (input.branch.match(RegExp(`refs/merge-requests/[1-9][0-9]*/head`)) != null ||
            input.branch.match(RegExp(`refs/changes/[1-9][0-9]*/head`)) != null)
        ) {
            step.info(`git clone ${input.branch} ${arg1} ${arg2} ${arg3} ${input.projectDir}`)
            const cloneOutput = (
                await git.clone(
                    input.workDir,
                    input.projectDir,
                    input.cloneUrl,
                    '',
                    arg1,
                    arg2,
                    arg3
                )
            )

            step.info(masking(cloneOutput.stdout))
            exitCode = cloneOutput.exitCode
            if (exitCode != 0) {
                const sanitizedUrl = util.maskPassword(input.cloneUrl);
                step.info(
                    `git clone ${sanitizedUrl} ${input.branch} ${arg1} ${arg2} ${arg3} ${input.projectDir} error`
                )
                throw new Error('git clone error')
            }
            let replaceValue = input.branch.match(RegExp(`[1-9][0-9]*`))
            let tmpLocalBranch = ''
            if (input.branch.match(RegExp(`refs/merge-requests/[1-9][0-9]*/head`)) != null) {
                tmpLocalBranch = input.branch.replace(
                    RegExp(`refs/merge-requests/[1-9][0-9]*/head`),
                    'merge-request_' + replaceValue
                )
            } else {
                tmpLocalBranch = input.branch.replace(
                    RegExp(`refs/changes/[1-9][0-9]*/head`),
                    'code-review_' + replaceValue
                )
            }

            let remoteRef = input.branch.replace('refs/', '')
            step.info(
                `[Checkout_temp_branch] git fetch origin ${remoteRef}:${tmpLocalBranch} and git checkout from ${input.branch} to ${tmpLocalBranch}`
            )
            await git.sshCommand(
                input.projectDir,
                `ssh -i '${input.workDir}${util.SEP}tempssh${util.SEP}id_rsa_custom'`
            )
            let fetchOutput = await git.fetch(
                input.projectDir,
                'origin',
                `${remoteRef}:${tmpLocalBranch}`
            )
            if (fetchOutput.exitCode != 0) {
                throw new Error(`git fetch origin ${remoteRef}:${tmpLocalBranch} error`)
            }
            await git.checkout(input.projectDir, tmpLocalBranch)
            if (input.submodule === true) {
                await git.submodule(input.projectDir, 'update', '--init', '--recursive')
            }
        } else {
            //step.info(`git clone ${cloneUrl} ${branch} ${arg1} ${arg2} ${arg3} ${projectDir}`)
            const cloneOutput = (
                await git.clone(
                    input.workDir,
                    input.projectDir,
                    input.cloneUrl,
                    `${input.branch}`,
                    arg1,
                    arg2,
                    arg3
                )
            )

            step.info(masking(cloneOutput.stdout))
            exitCode = cloneOutput.exitCode
            if (exitCode != 0) {
                const sanitizedUrl = util.maskPassword(input.cloneUrl);
                throw new Error(`git clone ${sanitizedUrl} ${input.branch} ${arg1} ${arg2} ${arg3} ${input.projectDir} error`)
            }
        }
        if (exitCode === 0) {
            if (input.commitId != undefined && input.commitId != 'null') {
                await git.reset(input.projectDir, input.commitId, '--hard')
            }
        }
}

export async function cleanLocalChange(projectDir: string, submodule: boolean) {
    step.info(
        `The project directory has been existed and the source not changed, clean local changes that not commited`
    )
    let git = new GitTools()
    //let fileList = (await git.listFiles(projectDir, '--exclude-standard')).stdout.split(',').join(' ')
    git.reset(projectDir, undefined, 'HEAD')
    let modifiedFiles = (
        await git.listFiles(projectDir, '--modified', '--exclude-standard')
    ).stdout
        .split(',')
        .join(' ')
    if (modifiedFiles != ' ' && modifiedFiles != '') {
        git.checkout(projectDir, modifiedFiles)
    }
    step.info(`clean local change that not commited`)
    let output = await git.listFiles(
        projectDir,
        '--others',
        '--exclude-standard'
    )
    if (output.stdout != '' && output.stdout != ' ') {
        let untracedFiles = output.stdout.split(',')
        untracedFiles.forEach(value => {
            if (value != '' && value != ' ') {
                let fileFullName: string = projectDir + `${util.SEP}` + value
                if (fs.existsSync(fileFullName)) {
                    fs.unlinkSync(fileFullName)
                }
            }
        })
    }
    if (submodule == true) {
        await git.submodule(projectDir, 'deinit', '-f', '.')
    }
}

export async function handleFlowSource(input: IGitSourceParams) {
    step.info(`Use Flow pipeline as source`)
    step.info(
        `Sync source pipeline ${input.gitRepo}#${input.branch} artifact to ${input.projectDir}`
    )
    let packages = input.packages
    if (packages != undefined) {
        if (fs.existsSync(input.projectDir)) {
            util.clearDir(input.projectDir)
        } else {
            fs.mkdirSync(input.projectDir)
        }
        for (let i = 0; i < packages.length; i++) {
            let bucketName = packages[i].bucketName
            let objectKey: string = packages[i].objectKey
            let fileName = path.basename(objectKey)
            let filePath =
                input.projectDir + util.SEP + packages[i].packageLabel
            let localPath = filePath + util.SEP + fileName
            step.info(`Download ${objectKey} to ${localPath}`)
            fs.mkdirSync(filePath)
            await ossUtil.get(
                objectKey,
                localPath,
                input.user,
                input.password,
                input.token,
                bucketName
            )
        }
    }
}

export async function handleJenkinsSource(input: IGitSourceParams) {
    step.info(`Use Jenkins as source`)
    let master = input.gitRepo
    if (
        input.host != undefined &&
        input.host != 'null' &&
        input.jobPath != undefined &&
        input.jobPath != 'null'
    ) {
        master = input.host + input.jobPath
    }
    let build = input.branch
    if (build === undefined) {
        build = 'lastSuccessfulBuild'
    }
    if (fs.existsSync(input.projectDir)) {
        util.clearDir(input.projectDir)
    } else {
        fs.mkdirSync(input.projectDir)
    }

    let baseUrl = master + '/' + build
    let buildApiUrl = `${baseUrl}/api/json`
    let encryptStr = Buffer.from(
        `${input.user}:${input.password}`,
        'utf-8'
    ).toString('base64')
    step.info(`Check Jenkins input source: ${baseUrl}`)
    let config1 = {
        method: 'get',
        url: `${buildApiUrl}`,
        headers: {
            Authorization: `Basic ${encryptStr}`
        }
    }
    let response1 = await axios(config1)
    if (response1.status === 404 || response1.data.artifacts.length === 0) {
        step.info(`${baseUrl}:Resource not found, check if the version exists.`)
        throw new Error('Resource not found, check if the version exists')
    }
    if (response1.data.class === '') {
        step.info(
            'The current Jenkins configuration is illegal, and the current workspace initialization is complete.'
        )
        return
    } else {
        step.info(`Synchronize workspace information: ${baseUrl}`)
        let config2 = {
            method: 'get',
            url: `${baseUrl}/api/json?tree=artifacts%5BrelativePath%5D`,
            headers: {
                Authorization: `Basic ${encryptStr}`
            }
        }
        let response2 = await axios(config2)
        let artifactList = response2.data.artifacts
        for (let i = 0; i < artifactList.length; i++) {
            let artifactFullName = artifactList[i].relativePath
            step.info(`downloading artifact ${artifactFullName}`)
            if (
                !fs.existsSync(
                    `${input.projectDir}${util.SEP}` +
                        path.dirname(artifactFullName)
                )
            ) {
                fs.mkdirSync(
                    `${input.projectDir}${util.SEP}` +
                        path.dirname(artifactFullName)
                )
            }
            let config3 = {
                method: 'get',
                url: `${baseUrl}/artifact/${artifactFullName}`,
                headers: {
                    Authorization: `Basic ${encryptStr}`
                },
                responseType: 'arraybuffer' as const // 指定响应类型为 'arraybuffer'，确保二进制数据以正确的格式接收
            }
            await axios(config3)
                .then(res => {
                    fs.writeFileSync(
                        `${input.projectDir}${util.SEP}${artifactFullName}`,
                        res.data
                    )
                    step.info(`downloaded artifact ${artifactFullName}`)
                })
                .catch(err => {
                    step.info(`Download ${artifactFullName} error`)
                    throw new Error(`Download artifact error`)
                })
        }
    }
}
